# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

__revision__ = '$Hg:$'

# system imports
from OpenSSL import SSL, crypto
import time

# DDB imports
from ddb.log import log
from ddb.transport import ITransport, ITransportProtocol
from ddb import html as dhtml
from ddb.settings import *
from ddb.event import event
from ddb.user import user as IUser, userRegister
from ddb.threads import threaded

# Twisted imports
from twisted.web import http, resource, static, server
from twisted.web.html import escape
from twisted.python import failure
from twisted.internet import protocol, reactor, ssl
try:
    from twisted.protocols._c_urlarg import unquote, quote
except ImportError:
    from urllib import unquote, quote

class httpHandleException(resource.Resource):
    def __init__(self, exception):
        self.exception = exception

    def render(self, request):
        return """
<html>
<head>
<title>DDB %s</title>
<link rel="stylesheet" href="/static/style.css" type="text/css" />
</head>
<body>
<h1>Exception</h1>
<blockquote class="exception">
%s
</blockquote>
</body>
</html>""" % (DDB_VERSION, escape(str(self.exception)))

class httpRoot(resource.Resource):
    def render(self, request):
        return ''.join(dhtml.IPage().display(request))

class httpRootStatic(resource.Resource):
    def __init__(self):
        resource.Resource.__init__(self)

    def getChild(self, path, request):
        print 'GETCHILD', path, request, self.server
        return static.File('www/style.css')

class httpCommandOutput(resource.Resource):
    def __init__(self, output):
        resource.Resource.__init__(self)
        self.output = output

    def render(self, request):
        print "RENDER OUTPUT", self.output
        return """
<html>
<head>
<title>DDB %s</title>
<link rel="stylesheet" href="/static/style.css" type="text/css" />
</head>
<body>
<h1>output from <code>%s</code></h1>
<fieldset class="command">
<code>%s</code>
</fieldset>
</body>
</html>""" % (DDB_VERSION, request.uri, '<br />\n'.join(self.output))

class httpRootCommand(resource.Resource):
    output = []

    def getChild(self, path, request):
        self.output = []
        mask = 'httpRequest@%s' % request.client.host
        # parse args
        args = ''
        if request.args.has_key('args'):
            args = request.args['args'][0]
        self.transport = request.site
        user           = IUser('httpRequest', mask)
        try:
            event(request.site, 'command', user=user, command=path, args=args, request=request, reply=self.reply)
            return httpCommandOutput(self.output)
        except Exception, e:
            return httpHandleException(e)

    def render_GET(self, request):
        class CommandForm(dhtml.Form):
            formFields = [
                ['string', 'command', 'command', ''],
                ['string', 'args',    'args',    ''],
            ]
        return ''.join(dhtml.IPage('www/command.html')
            .display(request))

    def reply(self, output, e=None):
        print self.__class__, output
        self.output.append(escape(output))

class httpRequest(server.Request):

    def __init__(self, *args, **kwargs):
        server.Request.__init__(self, *args, **kwargs)

    def process(self):
        "Process a request."

        # get site from channel
        self.site = self.channel.site

        # set various default headers
        self.setHeader('server', 'DDB/%s' % DDB_VERSION)
        self.setHeader('date', http.datetimeToString())
        self.setHeader('content-type', "text/html")

        # Resource Identification
        self.prepath = []
        self.postpath = map(unquote, self.path[1:].split('/'))
        try:
            resrc = self.site.getResourceFor(self)
            self.render(resrc)
        except:
            self.processingFailed(failure.Failure())

class httpProtocol(ITransportProtocol):
    requestFactory = httpRequest

    def __init__(self):
        ITransportProtocol.__init__(self)

#class httpSSLContext(ssl.DefaultOpenSSLContextFactory):
#    def __init__(self):
#        ssl.DefaultOpenSSLContextFactory.__init__(self, 'config/ddb.key', 'config/ddb.crt')

# SSL Context
class httpSSLContext(ssl.DefaultOpenSSLContextFactory):
    def __init__(self, key, cert, cacert):
        self.mKey = key
        self.mCert = cert
        self.mCACert = cacert
        
    def verify(self, conn, cert, errnum, depth, ok):
        """Checks the certificate of an incoming connection"""
        return 1 # we don't care :-)
        
    def getContext(self):
        """Create an SSL context."""
        ctx = SSL.Context(SSL.SSLv2_METHOD)
        # Add the CA certificate(s)
        store = ctx.get_cert_store()
        for cert in self.mCACert:
            store.add_cert(cert)
        # Load the private key and certificate
        ctx.use_privatekey(self.mKey)
        ctx.use_certificate(self.mCert)
        ctx.set_verify(SSL.VERIFY_PEER | 
                SSL.VERIFY_FAIL_IF_NO_PEER_CERT, self.verify)
        ctx.set_verify_depth(len(self.mCACert))
        return ctx

class httpFactory(ITransportProtocol, server.Site):
    type           = 'http'
    requestFactory = httpRequest
    SSL            = False
    output         = []

    def __init__(self, resource, manager, config, doSSL=False):
        ITransportProtocol.__init__(self)
        server.Site.__init__(self, resource)
        self.manager = manager
        self.config  = config

    def reply(self, message):
        output.append(message)

class transport(ITransport):
    protocol = httpProtocol
    factory  = httpFactory

    def __init__(self, manager, name, config):
        ITransport.__init__(self, manager, name, config)
        log.msg('Starting %s' % str(self.__class__))
        try:
            serverPort = int(config['port'][0])
            try:
                serverHost = config['bind'][0]
            except KeyError:
                serverHost = '0.0.0.0'
            try:
                serverTimeout = int(config['timeout'][0])
            except KeyError:
                serverTimeout = 30
            try:
                serverSSL = int(config['ssl'][0])
            except:
                serverSSL = 0
        except KeyError, e:
            log.msg('Could not start transport, missing configuration option "%s"' % str(e))
            return
        self.root   = resource.Resource()
        self.root.putChild('',        httpRoot())
        self.root.putChild('command', httpRootCommand())
        self.root.putChild('static',  httpRootStatic())
        self.server = httpFactory(self.root, manager, config, serverSSL)
        if serverSSL:
            try:
                ssl_key  = crypto.load_privatekey (crypto.FILETYPE_PEM,
                    open('config/ddb.pem', 'r').read())
            except Exception, e:
                log.msg('Could not start transport, SSL private key loading failed: %s' % str(e))
                return
            try:
                ssl_cert = crypto.load_certificate(crypto.FILETYPE_PEM, 
                    open('config/ddb.pem', 'r').read())
            except Exception, e:
                log.msg('Could not start transport, SSL certificate loading failed: %s' % str(e))
                return
            self.server.SSL = True
            reactor.listenSSL(serverPort, self.server, httpSSLContext(ssl_key, ssl_cert, [ssl_cert]), 
                serverTimeout, serverHost)
        else:
            reactor.listenTCP(serverPort, self.server, serverTimeout, serverHost)

    def start(self):
        self.run = True

    def stop(self):
        self.run = False

# vim:ts=4:sw=4:et:
